/*
 * WriterSerializer.java
 *
 * Created on June 8, 2005, 10:02 PM
 *
 * (C) R. Alexander Milowski alex@milowski.com
 */

package org.infoset.xml.util;

import java.io.*;
import java.net.*;
import org.infoset.xml.*;

/**
 *
 * @author R. Alexander Milowski
 */
public class WriterSerializer implements Serializer
{

   Writer output;
   String encoding;
   boolean [] empty;
   boolean closeOnEnd;
   boolean escapeHigh;
   int level;
   
   /** Creates a new instance of WriterSerializer */
   public WriterSerializer(Writer output)
   {
      this(output,"UTF-8",false);
   }
   
   public WriterSerializer(Writer output,String encoding,boolean closeOnEnd)
   {
      this.output = output;
      this.encoding = encoding;
      this.empty = new boolean[XMLWriter.ARRAY_INCREMENT];
      this.escapeHigh = false;
      this.closeOnEnd = closeOnEnd;
      if (encoding!=null && encoding.equals("iso-8859")) {
         escapeHigh = true;
      }
   }
   
   public void setUseCharacterReferences(boolean flag) {
      escapeHigh = flag;
   }

   public void startElement(String prefix, Name name) throws XMLException
   {
      try {
         if (empty[level]) {
            output.write('>');
            empty[level] = false;
         }
         level++;
         if (level==empty.length) {
            boolean [] newempty = new boolean[empty.length+XMLWriter.ARRAY_INCREMENT];
            System.arraycopy(empty,0, newempty,0, empty.length);
            empty = newempty;
         }
         empty[level] = true;
         
         output.write('<');
         if (prefix!=Name.NO_PREFIX) {
            output.write(prefix);
            output.write(':');
         }
         output.write(name.getLocalName());
      } catch(IOException ex) {
         throw new XMLException("I/O error during write.",ex);
      }
   }
   public void endStartElement(Name name) { }

   public void attribute(String prefix, Name name,String value) throws XMLException
   {
      try {
         output.write(' ');
         if (prefix!=Name.NO_PREFIX) {
            output.write(prefix);
            output.write(':');
         }
         output.write(name.getLocalName());
         output.write("='");
         writeSafeString(value, true);
         output.write('\'');
      } catch(IOException ex) {
         throw new XMLException("I/O error during write.",ex);
      }
   }

   public void namespaceDeclaration(String prefix, URI namespace) throws XMLException
   {
      try {
         if (prefix==Name.NO_PREFIX) {
            output.write(" xmlns='");
         } else {
            output.write(" xmlns:");
            output.write(prefix);
            output.write("='");
         }
         if (namespace!=Name.NO_NAMESPACE) {
            output.write(namespace.toString());
         }
         output.write('\'');
      } catch(IOException ex) {
         throw new XMLException("I/O error during write.",ex);
      }
   }

   public void characters(String value,boolean cdataSection) throws XMLException
   {
      try {
         if (empty[level]) {
            output.write('>');
         }
         empty[level] = false;
         if (cdataSection) {
            output.write("<![CDATA[");
            output.write(value);
            output.write("]]>");
         } else {
            writeSafeString(value,false);
         }
      } catch(IOException ex) {
         throw new XMLException("I/O error during write.",ex);
      }
   }

   public void comment(String data) throws XMLException
   {
      try {
         if (empty[level]) {
            output.write('>');
         }
         empty[level] = false;
         output.write("<!--");
         output.write(data);
         output.write("-->");
      } catch(IOException ex) {
         throw new XMLException("I/O error during write.",ex);
      }
   }

   public void endElement(String prefix, Name name) throws XMLException
   {
      try {
         if (empty[level]) {
            output.write("/>");
         } else {
            output.write("</");
            if (prefix!=Name.NO_PREFIX) {
               output.write(prefix);
               output.write(':');
            }
            output.write(name.getLocalName());
            output.write('>');
         }
         level--;
      } catch(IOException ex) {
         throw new XMLException("I/O error during write.",ex);
      }
   }

   public void processingInstruction(Name target, String data) throws XMLException
   {
      try {
         if (empty[level]) {
            output.write('>');
         }
         empty[level] = false;
         output.write("<?");
         output.write(target.getLocalName());
         output.write(' ');
         output.write(data);
         output.write("?>");
      } catch(IOException ex) {
         throw new XMLException("I/O error during write.",ex);
      }
   }

   public void xmlDeclaration(String version, String encoding, String standalone) throws XMLException
   {
      try {
         output.write("<?xml version='");
         output.write(version);
         output.write('\'');
         if (this.encoding!=null) {
            output.write(" encoding='");
            output.write(this.encoding);
            output.write('\'');
         }
         if (standalone!=null) {
            output.write(" standalone='");
            output.write(standalone);
            output.write('\'');
         }
         output.write(" ?>\n");
      } catch (IOException ex) {
         throw new XMLException("Cannot write XML declaration due to I/O error",ex);
      }
   }

   public void startDocument() throws XMLException
   {
      level = 0;
      empty[level] = false;
   }

   public void endDocument() throws XMLException
   {
      try {
         output.write("\n");
         output.flush();
         if (closeOnEnd) {
            output.close();

         }
      } catch(IOException ex) {
         throw new XMLException("I/O error during write.",ex);
      }
   }
   
   void writeSafeString(String value,boolean attribute)
      throws java.io.IOException
   {
      if (value==null || value.length()==0) {
         return;
      }
      char [] chars = value.toCharArray();
      int start = 0,len = 0;
      while ((start+len)<chars.length) {
         switch (chars[start+len]) {
            case '&':
               if (len>0) {
                  output.write(chars,start,len);
               }
               start += len+1;
               len = 0;
               output.write("&amp;");
            break;
            case '<':
               if (len>0) {
                  output.write(chars,start,len);
               }
               start += len+1;
               len = 0;
               output.write("&lt;");
            break;
            case '\'':
               if (attribute) {
                  if (len>0) {
                     output.write(chars,start,len);
                  }
                  start += len+1;
                  len = 0;
                  output.write("&apos;");
               } else {
                  len++;
               }
               break;
            default:
               if (escapeHigh && chars[start+len]>255) {
                  if (len>0) {
                     output.write(chars,start,len);
                  }
                  start += len+1;
                  len = 0;
                  output.write("&#");
                  output.write(Integer.toString(chars[start+len]));
                  output.write(';');
               } else {
                  len++;
               }
         }
      }
      if (len!=0) {
         output.write(chars,start,len);
      }
   }

   public void end() throws XMLException
   {
      try {
         output.flush();
      } catch (IOException ex) {
         throw new XMLException("Error during flush of I/O.",ex);
      }
   }

   public void start() throws XMLException
   {
   }
   
}
